<!DOCTYPE html>
<html class="no-js" lang="zh">
  <head>
<meta charset="utf-8">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="alternate" href="https://www.desgard.com" hreflang="pt-BR">
<link href="http://gmpg.org/xfn/11" rel="profile">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="theme-color" content="#005344">
<title>Guardia · 瓜地</title>
<meta name="google-site-verification" content="zS1dSn20XtA4FJYEOQLXqI0boxZdMnJ2g3beje-cl20">
<meta name="description" content="I write many code to write less code.💻">
<meta name="keywords" content="">
<!-- Social: Facebook / Open Graph -->
<meta property="og:url" content="https://www.desgard.com/Shadowsocks-2/">
<meta property="og:title" content="       Shadowsocks Probe II - TCP 代理过程 | Gua  ">
<meta property="og:description" content="I write many code to write less code.💻">
<meta property="og:site_name" content="Desgard_Duan">
<meta property="og:locale" content="pt_BR">
<meta property="og:type" content="website">
<meta property="og:author" content="https://www.facebook.com/desgard.duan">
<meta property="og:image" content="https://www.desgard.com">
<!-- Social: Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@nandomoreirame">
<meta name="twitter:domain" content="https://www.desgard.com">
<meta name="twitter:title" content="       Shadowsocks Probe II - TCP 代理过程 | Gua  ">
<meta name="twitter:description" content="I write many code to write less code.💻">
<meta name="twitter:image:src" content="https://www.desgard.com">
<!-- Favicons -->
<link rel="apple-touch-icon" sizes="114x114" href="https://www.desgard.com/assets/ico/apple-touch-icon-114-516f4e19976b9e4dbb77ad9b576831fe.png">
<link rel="apple-touch-icon" sizes="72x72" href="https://www.desgard.com/assets/ico/apple-touch-icon-72-5409b2df229305703caf583d86c845ab.png">
<link rel="apple-touch-icon" href="https://www.desgard.com/assets/ico/apple-touch-icon-57-aa873e019cf659e0d4e6a0b5bb9f379d.png">
<link rel="shortcut icon" href="https://www.desgard.com/assets/ico/favicon-4298be3d3fbe23e18d65bded9d930899.png">
<!-- rel prev and next -->
<!-- Canonical link tag -->
<link rel="canonical" href="https://www.desgard.com/Shadowsocks-2/">
<link rel="alternate" type="application/rss+xml" title="Gua" href="https://www.desgard.com/feed.xml">
<link rel="stylesheet" href="/assets/main-0b7b828712f4c43b75bba4861c907bb1.css">
<script src="/assets/modernizr-custom-cb807611a3e262b2eac59444cbab74d6.js" data-cfasync="false"></script>
<script src="/assets/js/jquery-3.1.js"></script>
<script src="/assets/js/jquery.rotate.js"></script>
<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "Website",
  "publisher": "www.desgard.com",
  "url": "http://www.desgard.com/",
  "description": "瓜地"
}
</script>
<script type="text/javascript">
  var disqus_shortname = 'desgard',
      baseurl          = '';
</script>
<!--
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-52446115-1']);
_gaq.push(['_trackPageview']);
(function() {
  var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>-->
<script>
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "//hm.baidu.com/hm.js?c5a8123bc51782a3083a631ed9ff50e4";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
</script>
<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
      tex2jax: {
        inlineMath: [ ['$','$'] ],
        displayMath: [ ['$$','$$'] ],
        processEscapes: true
      }
    });
  </script>
  </head>
  <body class="post-template single">
    <header class="header">
  <div class="container">
    <h1><a href="/"><strong>desgard</strong>.com</a></h1>
    <nav class="navbar">
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="#!" data-modal="modalSearch"><i class="fa fa-search"></i></a></li>
        <li><a href="/comment " target="_blank"><i class="fa fa-comments"></i></a></li>
        <li><a href="/feed.xml" target="_blank"><i class="fa fa-feed"></i></a></li>
      </ul>
    </nav>
  </div>
</header>
    <main class="wrapper container" itemprop="mainContentOfPage" itemscope="itemscope" itemtype="http://schema.org/Blog">
      <article class="post" itemscope="itemscope" itemtype="http://schema.org/BlogPosting" itemprop="blogPost">
  <header class="post-header">
    <h1>Shadowsocks Probe II - TCP 代理过程</h1>
    <div class="post-meta">
      <time datetime="2015-10-13">
        <i class="fa fa-calendar-o"></i> <time datetime="2017-11-12"> 2017-11-12
      </time>
      <span>
        <i class="fa fa-comments"></i> <a href="#container">Comment me!</a>
      </span>
      <span>
<!--
        <i class="fa fa-folder-open-o"></i> 
-->
      </span>
    </div>
  </header>
  <blockquote>
  <p>（续 <a href="http://www.desgard.com/Shadowsocks-1/">Shadowsocks Probe I - Socks5 与 EventLoop 事件分发</a>）</p>
</blockquote>
<h2 id="tcprelay--socket-">TCPRelay 及 Socket 监听事件</h2>
<p>前文我们了解了 Shadowsocks 通过 <code>EventLoop</code> 的循环，整体构建了一个 <code>Reactor</code> 模式来将时间逐层传递给 <code>EventHandler</code>，然后在进行 I/O 处理。</p>
<p>我们再次查看 <code>handle_event</code> 的代码进行跟踪：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">handle_event</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="n">sock</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> <span class="n">event</span><span class="p">):</span>
   <span class="c1"># 处理 events 并派发到指定的 handlers</span>
   <span class="k">if</span> <span class="ss">sock</span><span class="p">:</span>
       <span class="n">logging</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="n">shell</span><span class="o">.</span><span class="n">VERBOSE_LEVEL</span><span class="p">,</span> <span class="s1">&#39;fd %d %s&#39;</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span>
                   <span class="n">eventloop</span><span class="o">.</span><span class="n">EVENT_NAMES</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="n">event</span><span class="p">))</span>
   <span class="c1"># 如果是 TCPRelay 的 socket</span>
   <span class="c1"># 这时候说明有 TCP 连接,创建 TCPRelayHandler 并封装</span>
   <span class="k">if</span> <span class="n">sock</span> <span class="o">==</span> <span class="nb">self</span><span class="o">.</span><span class="n">_server_socket</span><span class="p">:</span>
       <span class="k">if</span> <span class="n">event</span> <span class="o">&amp;</span> <span class="n">eventloop</span><span class="o">.</span><span class="n">POLL_ERR</span><span class="p">:</span>
           <span class="c1"># TODO</span>
           <span class="k">raise</span> <span class="no">Exception</span><span class="p">(</span><span class="s1">&#39;server_socket error&#39;</span><span class="p">)</span>
       <span class="ss">try</span><span class="p">:</span>
           <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">&#39;accept&#39;</span><span class="p">)</span>
           <span class="c1"># 接受连接</span>
           <span class="n">conn</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">_server_socket</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
           <span class="c1"># 完成 handler 封装</span>
           <span class="no">TCPRelayHandler</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="nb">self</span><span class="o">.</span><span class="n">_fd_to_handlers</span><span class="p">,</span>
                           <span class="nb">self</span><span class="o">.</span><span class="n">_eventloop</span><span class="p">,</span> <span class="n">conn</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span><span class="p">,</span> <span class="nb">self</span><span class="o">.</span><span class="n">_config</span><span class="p">,</span>
                           <span class="nb">self</span><span class="o">.</span><span class="n">_dns_resolver</span><span class="p">,</span> <span class="nb">self</span><span class="o">.</span><span class="n">_is_local</span><span class="p">)</span>
       <span class="n">except</span> <span class="p">(</span><span class="no">OSError</span><span class="p">,</span> <span class="no">IOError</span><span class="p">)</span> <span class="n">as</span> <span class="ss">e</span><span class="p">:</span>
           <span class="n">error_no</span> <span class="o">=</span> <span class="n">eventloop</span><span class="o">.</span><span class="n">errno_from_exception</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
           <span class="k">if</span> <span class="n">error_no</span> <span class="k">in</span> <span class="p">(</span><span class="n">errno</span><span class="o">.</span><span class="n">EAGAIN</span><span class="p">,</span> <span class="n">errno</span><span class="o">.</span><span class="n">EINPROGRESS</span><span class="p">,</span>
                           <span class="n">errno</span><span class="o">.</span><span class="n">EWOULDBLOCK</span><span class="p">):</span>
               <span class="k">return</span>
           <span class="k">else</span><span class="p">:</span>
               <span class="n">shell</span><span class="o">.</span><span class="n">print_exception</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
               <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">_config</span><span class="o">[</span><span class="s1">&#39;verbose&#39;</span><span class="o">]</span><span class="p">:</span>
                   <span class="n">traceback</span><span class="o">.</span><span class="n">print_exc</span><span class="p">()</span>
   <span class="k">else</span><span class="p">:</span>
       <span class="k">if</span> <span class="ss">sock</span><span class="p">:</span>
           <span class="c1"># 找到 fd 对应的 TCPRelayHandler</span>
           <span class="n">handler</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">_fd_to_handlers</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="no">None</span><span class="p">)</span>
           <span class="k">if</span> <span class="ss">handler</span><span class="p">:</span>
               <span class="c1"># 启用 handler 来处理读写事件</span>
               <span class="n">handler</span><span class="o">.</span><span class="n">handle_event</span><span class="p">(</span><span class="n">sock</span><span class="p">,</span> <span class="n">event</span><span class="p">)</span>
       <span class="k">else</span><span class="p">:</span>
           <span class="n">logging</span><span class="o">.</span><span class="n">warn</span><span class="p">(</span><span class="s1">&#39;poll removed fd&#39;</span><span class="p">)</span></code></pre></div>
<p>其实在源码中，作者对客户端链接建立的完全过程做了注释：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># for each opening port, we have a TCP Relay</span>
<span class="c1"># 对于每一个开启的端口，都会拥有一个 TCPRelay</span>
<span class="c1"># for each connection, we have a TCP Relay Handler to handle the connection</span>
<span class="c1"># 对于每一个连接请求，都有一个 TCPRelayHandler 来持有</span>
<span class="c1"># for each handler, we have 2 sockets:</span>
<span class="c1">#    local:   connected to the client</span>
<span class="c1">#    remote:  connected to remote server</span>
<span class="c1"># 对于每一个 handler，有两种 Sockets</span>
<span class="c1">#     local: 用于链接到客户端</span>
<span class="c1"># 	  remote: 用于链接到远程服务器</span></code></pre></div>
<p>这里解释了每个对象的作用。其中，<code>TCPRelay</code> 由 <code>_server_socket</code> 来记录监听端口的 Socket，<code>_listen_port</code> 和 <code>_listen_addr</code> 分别代表监听的端口和地址。另外还有很多属性来记录各种状态，例如 <code>_config</code> 来记录配置，<code>_is_local</code> 记录是否为本地客户端请求，<code>_fd_to_handler</code> 持有对应的 handler 的映射关系。</p>
<p>如果产生 Event 的 Socket 是 <code>TCPRelay</code> 本身，那么 <code>accept()</code> 之后会建立一个新的 TCP 连接，并创建 <code>TCPRelayHandler</code> 对象来负责处理，这种事件用于 Client 端和 Proxy 端的连接和传递；否则说明 Event 的 Socket 为 <code>TCPRelayHandler</code> 的，这时候 <code>fd</code> 会检索到相关 <code>handler</code> 并调用 <code>handle_event</code> 来处理 Event。</p>
<p>在继续阅读之前，我们先来回顾 Event 在 <code>epoll</code> 的操作过程中可能出现的一些状态，以 C 代码中的宏名称为标准：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">EPOLLIN</span> <span class="err">：表示对应的文件描述符可以读（包括对端</span><span class="no">SOCKET</span><span class="err">正常关闭）；</span>
<span class="no">EPOLLOUT</span><span class="err">：表示对应的文件描述符可以写；</span>
<span class="no">EPOLLPRI</span><span class="err">：表示对应的文件描述符有紧急的数据可读（这里应该表示有带外数据到来）；</span>
<span class="no">EPOLLERR</span><span class="err">：表示对应的文件描述符发生错误；</span>
<span class="no">EPOLLHUP</span><span class="err">：表示对应的文件描述符被挂断；</span>
<span class="no">EPOLLET</span><span class="err">：</span> <span class="err">将</span><span class="no">EPOLL</span><span class="err">设为边缘触发</span><span class="p">(</span><span class="no">Edge</span> <span class="no">Triggered</span><span class="p">)</span><span class="err">模式，这是相对于水平触发</span><span class="p">(</span><span class="no">Level</span> <span class="no">Triggered</span><span class="p">)</span><span class="err">来说的。</span>
<span class="no">EPOLLONESHOT</span><span class="err">：只监听一次事件，当监听完这次事件之后，如果还需要继续监听这个</span><span class="n">socket</span><span class="err">的话，需要再次把这个</span><span class="n">socket</span><span class="err">加入到</span><span class="no">EPOLL</span><span class="err">队列里</span></code></pre></div>
<p>在 <code>eventloop.py</code> 中我们找到了这些对应状态：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">POLL_NULL</span> <span class="o">=</span> <span class="mh">0x00</span>
<span class="no">POLL_IN</span> <span class="o">=</span> <span class="mh">0x01</span>
<span class="no">POLL_OUT</span> <span class="o">=</span> <span class="mh">0x04</span>
<span class="no">POLL_ERR</span> <span class="o">=</span> <span class="mh">0x08</span>
<span class="no">POLL_HUP</span> <span class="o">=</span> <span class="mh">0x10</span>
<span class="no">POLL_NVAL</span> <span class="o">=</span> <span class="mh">0x20</span>
<span class="no">EVENT_NAMES</span> <span class="o">=</span> <span class="p">{</span>
    <span class="ss">POLL_NULL</span><span class="p">:</span> <span class="s1">&#39;POLL_NULL&#39;</span><span class="p">,</span> 	<span class="c1"># 事件为空</span>
    <span class="ss">POLL_IN</span><span class="p">:</span> <span class="s1">&#39;POLL_IN&#39;</span><span class="p">,</span>		    <span class="c1"># 有数据可读</span>
    <span class="ss">POLL_OUT</span><span class="p">:</span> <span class="s1">&#39;POLL_OUT&#39;</span><span class="p">,</span>		<span class="c1"># 写数据不会导致阻塞</span>
    <span class="ss">POLL_ERR</span><span class="p">:</span> <span class="s1">&#39;POLL_ERR&#39;</span><span class="p">,</span>		<span class="c1"># 指定的文件描述符发生错误（revents 域）</span>
    <span class="ss">POLL_HUP</span><span class="p">:</span> <span class="s1">&#39;POLL_HUP&#39;</span><span class="p">,</span>		<span class="c1"># 指定的文件描述符挂起事件</span>
    <span class="ss">POLL_NVAL</span><span class="p">:</span> <span class="s1">&#39;POLL_NVAL&#39;</span><span class="p">,</span>	    <span class="c1"># 指定的文件描述符非法</span>
<span class="p">}</span></code></pre></div>
<p>而 <code>TCPRelay</code> 在循环中拿到的拿到的 Event 只有状态为 <code>POLL_IN</code> 才能为其分配指定的 <code>handle</code>。而在分配之前，前面的那个代码片段仅仅是判断了 <code>POLL_ERR</code> 这种 <code>fd</code> 发生错误的状况。于是去查看一下 Event 投放的入口 <code>add_to_loop</code> 方法：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">add_to_loop</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="kp">loop</span><span class="p">):</span>
    <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">_eventloop</span><span class="p">:</span>
        <span class="k">raise</span> <span class="no">Exception</span><span class="p">(</span><span class="s1">&#39;already add to loop&#39;</span><span class="p">)</span>
    <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">_closed</span><span class="p">:</span>
        <span class="k">raise</span> <span class="no">Exception</span><span class="p">(</span><span class="s1">&#39;already closed&#39;</span><span class="p">)</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">_eventloop</span> <span class="o">=</span> <span class="kp">loop</span>
    <span class="c1"># 仅仅向 loop 中投递两种状态的 event</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">_eventloop</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">_server_socket</span><span class="p">,</span>
                        <span class="n">eventloop</span><span class="o">.</span><span class="n">POLL_IN</span> <span class="o">|</span> <span class="n">eventloop</span><span class="o">.</span><span class="n">POLL_ERR</span><span class="p">,</span> <span class="nb">self</span><span class="p">)</span>
    <span class="c1"># 为循环增加周期</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">_eventloop</span><span class="o">.</span><span class="n">add_periodic</span><span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">handle_periodic</span><span class="p">)</span></code></pre></div>
<p>在 <code>add_to_loop</code> 中仅注册了可读事件，所以不会出现其他的情况。</p>
<p>在前文中，我们说一个 <code>TCPRelay</code> 代表的其实就是一个普通的 TCP 服务器，其中持有了所监听的端口(<code>_listen_port</code>)和 IP 地址(<code>_listen_addr</code>)。但是我们在使用 Shadowsocks 时，在 PAC 过滤文件中仅仅用域名字符串数组的形式来存储，并无法得知其 IP，那我们的 <code>TCPRelay</code> 如何对事件对指定的地址进行转发呢？我们可以在 <code>__init__</code> 构造方法的代码中发现答案：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">addrs</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">getaddrinfo</span><span class="p">(</span><span class="n">listen_addr</span><span class="p">,</span> <span class="n">listen_port</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span>
                           <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_STREAM</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SOL_TCP</span><span class="p">)</span>
<span class="n">af</span><span class="p">,</span> <span class="n">socktype</span><span class="p">,</span> <span class="n">proto</span><span class="p">,</span> <span class="n">canonname</span><span class="p">,</span> <span class="n">sa</span> <span class="o">=</span> <span class="n">addrs</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span>
<span class="n">server_socket</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span><span class="n">af</span><span class="p">,</span> <span class="n">socktype</span><span class="p">,</span> <span class="n">proto</span><span class="p">)</span>
<span class="n">server_socket</span><span class="o">.</span><span class="n">setsockopt</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">SOL_SOCKET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SO_REUSEADDR</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">server_socket</span><span class="o">.</span><span class="n">bind</span><span class="p">(</span><span class="n">sa</span><span class="p">)</span></code></pre></div>
<p>不禁要问 <code>getaddrinfo</code> 方法是什么，问什么能拿到这么多信息？我们用 <code>iPython</code> 直接调试一下这个方法：</p>
<p><img src="../assets/images/blog/15027549268791/15033139083269.jpg" alt="" /></p>
<p>可以发现 <code>getaddrinfo</code> 可以对域名做地址解析。并且通过<a href="http://python.usyiyi.cn/translate/python_278/library/socket.html#socket.getaddrinfo">文档</a>查询该方法会将<strong>主机参数转换为包含用于创建连接到该服务的 Socket 的所有必须参数的 5 元祖集合</strong>。其函数范式如下：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">socket</span><span class="o">.</span><span class="n">getaddrinfo</span><span class="p">(</span><span class="n">host</span><span class="p">,</span> <span class="n">port</span><span class="o">[</span><span class="p">,</span> <span class="n">family</span><span class="o">[</span><span class="p">,</span> <span class="n">socktype</span><span class="o">[</span><span class="p">,</span> <span class="n">proto</span><span class="o">[</span><span class="p">,</span> <span class="n">flags</span><span class="o">]]]]</span><span class="p">)</span>
<span class="o">&gt;</span> <span class="k">return</span> <span class="p">(</span><span class="n">family</span><span class="p">,</span> <span class="n">socktype</span><span class="p">,</span> <span class="n">proto</span><span class="p">,</span> <span class="n">canonname</span><span class="p">,</span> <span class="n">sockaddr</span><span class="p">)</span></code></pre></div>
<p>由此可以看出，这个方法可以获取到主机的全部信息，是 <code>TCPRelay</code> 成功监听的关键之一。</p>
<p><img src="../assets/images/blog/15027549268791/TCPRelay.description.png" alt="TCPRelay.description" /></p>
<h2 id="tcprelayhandler">TCPRelayHandler</h2>
<p>将 <code>TCPRelay</code> 转发给 <code>TCPRelayHandler</code> 之后，<code>handle_event</code> 会对应选择处理的事件。它会再进一步，根据事件的种类，调用相应的函数来做处理，这也是一个转发的过程。注意，这里的 <code>handle_event</code> 是由 <code>TCPRelayHandler</code> 实例的方法属性，而不是上文中出现的 <code>TCPRelay</code> 中的属性。在这个方法中，会追踪 Socket 来决策调用方法。</p>
<p>在看源码之前，我们先来了解一下 <code>stage</code> 过程是如何定义的（还是在 <code>tcprelay.py</code> 中）：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># as ssserver:</span>
<span class="c1"># stage 0 初始状态，SOCKS5 握手，仅仅需要跳入 STAGE_ADDR 状态即可</span>
<span class="c1"># stage 1 SOCKS5 建立连接阶段，从本地获取地址（addr），DNS 进行解析</span>
<span class="c1"># stage 3 DNS 解析后连接到远端</span>
<span class="c1"># stage 4 从本地获取数据，保持连接</span>
<span class="c1"># stage 5 建立管道（pipe），SOCK5 传输</span>
<span class="no">STAGE_INIT</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">STAGE_ADDR</span> <span class="o">=</span> <span class="mi">1</span>
<span class="no">STAGE_UDP_ASSOC</span> <span class="o">=</span> <span class="mi">2</span>
<span class="no">STAGE_DNS</span> <span class="o">=</span> <span class="mi">3</span>
<span class="no">STAGE_CONNECTING</span> <span class="o">=</span> <span class="mi">4</span>
<span class="no">STAGE_STREAM</span> <span class="o">=</span> <span class="mi">5</span>
<span class="no">STAGE_DESTROYED</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span></code></pre></div>
<p>了解了各个 <code>stage</code> 过程的定义，我们来看下 <code>handle_event</code> 是如何根据 <code>stage</code> 过程种类来调用相应的函数的。这个过程也就是我们了解的<strong>转发过程</strong>：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">handle_event</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="n">sock</span><span class="p">,</span> <span class="n">event</span><span class="p">):</span>
    <span class="c1"># 处理所有事件并派发给指定的方法</span>
    <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">_stage</span> <span class="o">==</span> <span class="ss">STAGE_DESTROYED</span><span class="p">:</span>
        <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">&#39;ignore handle_event: destroyed&#39;</span><span class="p">)</span>
        <span class="k">return</span>
    <span class="c1"># 处理顺序很重要，优先级相关</span>
    <span class="c1"># 处理 self._romte_sock Socket </span>
    <span class="k">if</span> <span class="n">sock</span> <span class="o">==</span> <span class="nb">self</span><span class="o">.</span><span class="n">_remote_sock</span><span class="p">:</span>
        <span class="c1"># 文件描述符错误，直接进入 STAGE_DESTROYED 阶段</span>
        <span class="k">if</span> <span class="n">event</span> <span class="o">&amp;</span> <span class="n">eventloop</span><span class="o">.</span><span class="n">POLL_ERR</span><span class="p">:</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">_on_remote_error</span><span class="p">()</span>
            <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">_stage</span> <span class="o">==</span> <span class="ss">STAGE_DESTROYED</span><span class="p">:</span>
                <span class="k">return</span>
        <span class="c1"># 有数据可读或已经被挂起，进行读数据事件</span>
        <span class="k">if</span> <span class="n">event</span> <span class="o">&amp;</span> <span class="p">(</span><span class="n">eventloop</span><span class="o">.</span><span class="n">POLL_IN</span> <span class="o">|</span> <span class="n">eventloop</span><span class="o">.</span><span class="n">POLL_HUP</span><span class="p">):</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">_on_remote_read</span><span class="p">()</span>
            <span class="c1"># 如果已经进入 STAGE_DESTROYED 结束处理</span>
            <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">_stage</span> <span class="o">==</span> <span class="ss">STAGE_DESTROYED</span><span class="p">:</span>
                <span class="k">return</span>
        <span class="c1"># 如果需要写数据，进行写数据事件</span>
        <span class="k">if</span> <span class="n">event</span> <span class="o">&amp;</span> <span class="n">eventloop</span><span class="o">.</span><span class="n">POLL_OUT</span><span class="p">:</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">_on_remote_write</span><span class="p">()</span>
    <span class="c1"># 处理 self._local_sock Socket</span>
    <span class="n">elif</span> <span class="n">sock</span> <span class="o">==</span> <span class="nb">self</span><span class="o">.</span><span class="n">_local_sock</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">event</span> <span class="o">&amp;</span> <span class="n">eventloop</span><span class="o">.</span><span class="n">POLL_ERR</span><span class="p">:</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">_on_local_error</span><span class="p">()</span>
            <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">_stage</span> <span class="o">==</span> <span class="ss">STAGE_DESTROYED</span><span class="p">:</span>
                <span class="k">return</span>
        <span class="k">if</span> <span class="n">event</span> <span class="o">&amp;</span> <span class="p">(</span><span class="n">eventloop</span><span class="o">.</span><span class="n">POLL_IN</span> <span class="o">|</span> <span class="n">eventloop</span><span class="o">.</span><span class="n">POLL_HUP</span><span class="p">):</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">_on_local_read</span><span class="p">()</span>
            <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">_stage</span> <span class="o">==</span> <span class="ss">STAGE_DESTROYED</span><span class="p">:</span>
                <span class="k">return</span>
        <span class="k">if</span> <span class="n">event</span> <span class="o">&amp;</span> <span class="n">eventloop</span><span class="o">.</span><span class="n">POLL_OUT</span><span class="p">:</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">_on_local_write</span><span class="p">()</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="c1"># 未知套接字直接打印 log</span>
        <span class="n">logging</span><span class="o">.</span><span class="n">warn</span><span class="p">(</span><span class="s1">&#39;unknown socket&#39;</span><span class="p">)</span></code></pre></div>
<p>以上代码中，<code>handle_event</code> 会根据事件发生的 Socket 决定调用方法，我们发现 <code>self._remote_sock</code> 和 <code>self._local_sock</code> 两个套接字，其执行方法是对称的。这两个套接字的作用分别是什么作用呢？用一张图来描述一下整个的约定：</p>
<p><img src="../assets/images/blog/15027549268791/agreement_new.png" alt="agreement_ne" /></p>
<p>这是上一篇文章中一张插图的修改，其中黑色圆点代表 <code>local_sock</code>，红色圆点代表 <code>remote_sock</code>。由于 Shadowsocks 源码是客户端和服务端复用的代码，所以对于 <code>sslocal</code> 和 <code>ssserver</code> 而言其 Socket 表示的含义不同：</p>
<ul>
  <li><code>sslocal</code> 而言：<code>local_sock</code> 指的是 SOCK5 客户端，<code>remote_sock</code> 指的是 <code>ssserver</code>；</li>
  <li><code>ssserver</code> 而言：<code>local_sock</code> 指的是 <code>sslocal</code>，<code>remote_sock</code> 指的是目标服务器。</li>
</ul>
<p>我们可以按照图示位置（想对与当前节点而言），将设备分为左端和右端。抽象出来 <code>local_sock</code> 是与左端设备通信的 Socket，而 <code>remote_sock</code> 是与右端设备通信的 Socket。这样我们就能发现其对称性，公用一套相同逻辑的代码。</p>
<h2 id="section">事件投递细节</h2>
<p>之前提及到的 <code>handle_event</code> 方法，当与左端通信的时候，会涉及到三个方法，并对应如下几种事件来定制处理，详细来看一下：</p>
<ul>
  <li><code>POLL_ERR</code>：<code>self._on_local_error()</code> 错误处理。</li>
  <li><code>POLL_IN</code> 或 <code>POLL_HUP</code>：<code>self._on_local_read()</code> 读事件。</li>
  <li><code>POLL_OUT</code>：<code>self._on_local_write()</code> 写事件。</li>
</ul>
<h3 id="onlocalread-"><code>_on_local_read</code> 方法</h3>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">_on_local_read</span><span class="p">(</span><span class="nb">self</span><span class="p">):</span>
    <span class="c1"># 处理所有的本地读事件并派发给不同 stage 处理方法</span>
    <span class="c1"># 判断非 sslocal 状态，预防判断</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="nb">self</span><span class="o">.</span><span class="n">_local_sock</span><span class="p">:</span>
        <span class="k">return</span>
    <span class="n">is_local</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">_is_local</span>
    <span class="n">data</span> <span class="o">=</span> <span class="no">None</span>
    <span class="c1"># 如果是 sslocal 则用上端极值</span>
    <span class="k">if</span> <span class="ss">is_local</span><span class="p">:</span>
        <span class="n">buf_size</span> <span class="o">=</span> <span class="no">UP_STREAM_BUF_SIZE</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="n">buf_size</span> <span class="o">=</span> <span class="no">DOWN_STREAM_BUF_SIZE</span>
    <span class="c1"># 接受至多 buf_size 大小的数据</span>
    <span class="ss">try</span><span class="p">:</span>
        <span class="n">data</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">_local_sock</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="n">buf_size</span><span class="p">)</span>
    <span class="c1"># 异常处理，分成两种情况</span>
    <span class="c1">#   1. 如果异常原因为 ETIMEOUT, EAGAIN, EWOULDBLOCK 直接 return</span>
    <span class="c1">#   2. 否则直接销毁当前的 TCPRelayHandler</span>
    <span class="n">except</span> <span class="p">(</span><span class="no">OSError</span><span class="p">,</span> <span class="no">IOError</span><span class="p">)</span> <span class="n">as</span> <span class="ss">e</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">eventloop</span><span class="o">.</span><span class="n">errno_from_exception</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="k">in</span> <span class="p">\</span>
                <span class="p">(</span><span class="n">errno</span><span class="o">.</span><span class="n">ETIMEDOUT</span><span class="p">,</span> <span class="n">errno</span><span class="o">.</span><span class="n">EAGAIN</span><span class="p">,</span> <span class="n">errno</span><span class="o">.</span><span class="n">EWOULDBLOCK</span><span class="p">):</span>
            <span class="k">return</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="ss">data</span><span class="p">:</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">destroy</span><span class="p">()</span>
        <span class="k">return</span>
    <span class="c1"># 计时器重置</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">_update_activity</span><span class="p">(</span><span class="n">len</span><span class="p">(</span><span class="n">data</span><span class="p">))</span>
    <span class="c1"># 如果 data 是 sslocal 发送来的，直接解密数据</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="ss">is_local</span><span class="p">:</span>
        <span class="n">data</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">_cryptor</span><span class="o">.</span><span class="n">decrypt</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
        <span class="c1"># 判断数据解析后是否合法</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="ss">data</span><span class="p">:</span>
            <span class="k">return</span>
    <span class="c1"># 多状态分类讨论，即状态机分类</span>
    <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">_stage</span> <span class="o">==</span> <span class="ss">STAGE_STREAM</span><span class="p">:</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">_handle_stage_stream</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
        <span class="k">return</span>
    <span class="n">elif</span> <span class="n">is_local</span> <span class="ow">and</span> <span class="nb">self</span><span class="o">.</span><span class="n">_stage</span> <span class="o">==</span> <span class="ss">STAGE_INIT</span><span class="p">:</span>
        <span class="c1"># jump over socks5 init</span>
        <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">_is_tunnel</span><span class="p">:</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">_handle_stage_addr</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
            <span class="k">return</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">_handle_stage_init</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
    <span class="n">elif</span> <span class="nb">self</span><span class="o">.</span><span class="n">_stage</span> <span class="o">==</span> <span class="ss">STAGE_CONNECTING</span><span class="p">:</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">_handle_stage_connecting</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
    <span class="n">elif</span> <span class="p">(</span><span class="n">is_local</span> <span class="ow">and</span> <span class="nb">self</span><span class="o">.</span><span class="n">_stage</span> <span class="o">==</span> <span class="no">STAGE_ADDR</span><span class="p">)</span> <span class="ow">or</span> <span class="p">\</span>
            <span class="p">(</span><span class="ow">not</span> <span class="n">is_local</span> <span class="ow">and</span> <span class="nb">self</span><span class="o">.</span><span class="n">_stage</span> <span class="o">==</span> <span class="no">STAGE_INIT</span><span class="p">):</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">_handle_stage_addr</span><span class="p">(</span><span class="n">data</span><span class="p">)</span></code></pre></div>
<p>该方法从 <code>local_sock</code> 中读取数据，并通过状态来派发给不同的方法进行处理。在 <code>_on_local_read</code> 的实现中可以看出状态机做出了以下方法映射：</p>
<ul>
  <li><code>STAGE_INIT</code>：<code>self._handle_stage_init</code></li>
  <li><code>STAGE_ADDR</code>：<code>self._handle_stage_addr</code></li>
  <li><code>STAGE_CONNECTING</code>：<code>self._handle_stage_connecting</code></li>
  <li><code>STAGE_STREAM</code>：<code>self_handle_stage_stream</code></li>
</ul>
<p>而其他的状态仅仅代表一个中间过程量标记，而不导致任何实质性的改变。当然这些状态对于 <code>TCPRelayHandler</code> 的状态划分是十分重要的。既然我们已经确定 <code>TCPRelayHandler</code> 是一个状态机，那么我们将 <code>sslocal</code> 和 <code>ssserver</code> 的状态转移列举出来，从而更清晰的把握整体行为驱动：</p>
<p><img src="../assets/images/blog/15027549268791/STAGE.png" alt="STAGE" /></p>
<p>而 <code>ssserver</code> 的状态流程在上述代码中也能找到一些差异：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">elif</span> <span class="p">(</span><span class="n">is_local</span> <span class="ow">and</span> <span class="nb">self</span><span class="o">.</span><span class="n">_stage</span> <span class="o">==</span> <span class="no">STAGE_ADDR</span><span class="p">)</span> <span class="ow">or</span> <span class="p">\</span>
       <span class="p">(</span><span class="ow">not</span> <span class="n">is_local</span> <span class="ow">and</span> <span class="nb">self</span><span class="o">.</span><span class="n">_stage</span> <span class="o">==</span> <span class="no">STAGE_INIT</span><span class="p">):</span>
   <span class="nb">self</span><span class="o">.</span><span class="n">_handle_stage_addr</span><span class="p">(</span><span class="n">data</span><span class="p">)</span></code></pre></div>
<p><code>not is_local</code> 对应的是从 <code>sslocal</code> 发送来的数据，所以 <code>ssserver</code> 真正的起始状态是 <code>STAGE_ADDR</code>，这也验证了上一篇文的一个观点：<strong><code>ssserver</code> 只起到中继作用，负责解密以后将数据转发给目标服务器，并不涉及 SOCK5 协议中任何一部分</strong>。而在 <code>STAGE_INIT</code> 阶段主要的工作是<strong>SOCKS5 握手</strong>，所以 <code>ssserver</code> 没有这个流程也是理所当然的。</p>
<h3 id="onlocalerror-"><code>_on_local_error</code> 方法</h3>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># 嗅探到是错误，写日志直接销毁</span>
<span class="k">def</span> <span class="nf">_on_local_error</span><span class="p">(</span><span class="nb">self</span><span class="p">):</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">&#39;got local error&#39;</span><span class="p">)</span>
    <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">_local_sock</span><span class="p">:</span>
        <span class="n">logging</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="n">eventloop</span><span class="o">.</span><span class="n">get_sock_error</span><span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">_local_sock</span><span class="p">))</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">destroy</span><span class="p">()</span></code></pre></div>
<h3 id="onlocalwrite-"><code>_on_local_write</code> 方法</h3>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">_on_local_write</span><span class="p">(</span><span class="nb">self</span><span class="p">):</span>
    <span class="c1"># 处理本地可写事件</span>
    <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">_data_to_write_to_local</span><span class="p">:</span>
        <span class="n">data</span> <span class="o">=</span> <span class="n">b</span><span class="s1">&#39;&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">_data_to_write_to_local</span><span class="p">)</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">_data_to_write_to_local</span> <span class="o">=</span> <span class="o">[]</span>
        <span class="c1"># 数据写入</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">_write_to_sock</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="nb">self</span><span class="o">.</span><span class="n">_local_sock</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="c1"># 更新 Socket 监听事件</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">_update_stream</span><span class="p">(</span><span class="no">STREAM_DOWN</span><span class="p">,</span> <span class="no">WAIT_STATUS_READING</span><span class="p">)</span></code></pre></div>
<p><code>_on_local_write</code> 的作用就是如果 <code>self._data_to_write_to_local</code> 中有数据，则完成写入 <code>self._local_sock</code>，否则更新 Socket 的细节。</p>
<h2 id="handle">事件处理细节（Handle）</h2>
<p>在上述的分析中，我们已经知道了 <code>TCPRelayHandler</code> 在接受事件中，除了根据事件种类进行分类投递以外，还需要区分此时状态机的状态，才能确定处理细节。<code>_handle_stage_[stage detail]</code> 方法负责了 SOCKS5 协议的相关通信，控制了 Shadowsocks 内部状态机的转移，是真个项目中的核心部分。</p>
<h3 id="handlestageinit"><code>_handle_stage_init</code></h3>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">_handle_stage_init</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
    <span class="ss">try</span><span class="p">:</span>
        <span class="c1"># 进行协商授权</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">_check_auth_method</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
    <span class="n">except</span> <span class="ss">BadSocksHeader</span><span class="p">:</span>
        <span class="c1"># 如果是 SOCKS5 协议头错误，授权失败</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">destroy</span><span class="p">()</span>
        <span class="k">return</span>
    <span class="n">except</span> <span class="ss">NoAcceptableMethods</span><span class="p">:</span>
        <span class="c1"># 验证错误，授权失败</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">_write_to_sock</span><span class="p">(</span><span class="n">b</span><span class="s1">&#39;\x05\xff&#39;</span><span class="p">,</span> <span class="nb">self</span><span class="o">.</span><span class="n">_local_sock</span><span class="p">)</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">destroy</span><span class="p">()</span>
        <span class="k">return</span>
    <span class="c1"># 写入 sock，代表握手成功</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">_write_to_sock</span><span class="p">(</span><span class="n">b</span><span class="s1">&#39;\x05\00&#39;</span><span class="p">,</span> <span class="nb">self</span><span class="o">.</span><span class="n">_local_sock</span><span class="p">)</span>
    <span class="c1"># 状态转移</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">_stage</span> <span class="o">=</span> <span class="no">STAGE_ADDR</span></code></pre></div>
<p><code>_handle_stage_init</code> 是 SOCKS5 认证方式的协商方法，其中通过 <code>_check_auth_method</code> 方法来检测协商授权。需要注意的是，由于整个 SOCKS5 握手过程都是在 <code>sslocal</code> 中完成的，所以这个方法只有可能被 <code>sslocal</code> 调用。</p>
<h3 id="handlestageaddr"><code>_handle_stage_addr</code></h3>
<p>这个方法较为繁琐，因为在 <code>STAGE_ADDR</code> 过程中要分为 <em>TCP</em>、<em>UDP</em> 两种方式分别处理，而且还要判断是否开启 <em>One Time Auth</em>（一次性授权） 功能。我们将 UDP 和 <em>One Time Auth</em> 的代码删去，单看 <em>TCP</em> 流程。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">_handle_stage_addr</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
    <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">_is_local</span><span class="p">:</span>
        <span class="c1"># 此处获取 CMD 字段，并进行检测</span>
        <span class="n">cmd</span> <span class="o">=</span> <span class="n">common</span><span class="o">.</span><span class="n">ord</span><span class="p">(</span><span class="n">data</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">cmd</span> <span class="o">==</span> <span class="ss">CMD_UDP_ASSOCIATE</span><span class="p">:</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">_stage</span> <span class="o">=</span> <span class="no">STAGE_UDP_ASSOC</span>
            <span class="k">return</span>
        <span class="n">elif</span> <span class="n">cmd</span> <span class="o">==</span> <span class="ss">CMD_CONNECT</span><span class="p">:</span>
            <span class="c1"># 此处使用集合切片</span>
            <span class="c1"># 只截取 VER CMD RSV</span>
            <span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="o">[</span><span class="mi">3</span><span class="p">:</span><span class="o">]</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">logging</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s1">&#39;unknown command %d&#39;</span><span class="p">,</span> <span class="n">cmd</span><span class="p">)</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">destroy</span><span class="p">()</span>
            <span class="k">return</span>
    <span class="c1"># 对切片数据进行解析，并保存在 header_result</span>
    <span class="n">header_result</span> <span class="o">=</span> <span class="n">parse_header</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">header_result</span> <span class="n">is</span> <span class="ss">None</span><span class="p">:</span>
        <span class="k">raise</span> <span class="no">Exception</span><span class="p">(</span><span class="s1">&#39;can not parse header&#39;</span><span class="p">)</span>
    <span class="c1"># 这里可以看出 header_result 如果合法，即为一个 4 元元祖</span>
    <span class="c1"># addrtype = ATYP</span>
    <span class="c1"># remote_addr = DST.ADDR</span>
    <span class="c1"># remote_port = DST.PORT</span>
    <span class="c1"># header_length = 1 + n + 2</span>
    <span class="n">addrtype</span><span class="p">,</span> <span class="n">remote_addr</span><span class="p">,</span> <span class="n">remote_port</span><span class="p">,</span> <span class="n">header_length</span> <span class="o">=</span> <span class="n">header_result</span>
    <span class="c1"># 用 unicode 进行解码</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">_remote_address</span> <span class="o">=</span> <span class="p">(</span><span class="n">common</span><span class="o">.</span><span class="n">to_str</span><span class="p">(</span><span class="n">remote_addr</span><span class="p">),</span> <span class="n">remote_port</span><span class="p">)</span>
    <span class="c1"># 暂停读操作</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">_update_stream</span><span class="p">(</span><span class="no">STREAM_UP</span><span class="p">,</span> <span class="no">WAIT_STATUS_WRITING</span><span class="p">)</span>
    <span class="c1"># 状态转移至 STAGE_DNS</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">_stage</span> <span class="o">=</span> <span class="no">STAGE_DNS</span>
    <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">_is_local</span><span class="p">:</span>
        <span class="c1"># 转发 SOCKS5 响应，完成简历连接过程</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">_write_to_sock</span><span class="p">((</span><span class="n">b</span><span class="s1">&#39;\x05\x00\x00\x01&#39;</span>
                             <span class="n">b</span><span class="s1">&#39;\x00\x00\x00\x00\x10\x10&#39;</span><span class="p">),</span>
                            <span class="nb">self</span><span class="o">.</span><span class="n">_local_sock</span><span class="p">)</span>
        <span class="c1"># 在数据存储在 Buffer 之前，进行加密（因为要发送到 ssserver）</span>
        <span class="n">data_to_send</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">_encryptor</span><span class="o">.</span><span class="n">encrypt</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">_data_to_write_to_remote</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">data_to_send</span><span class="p">)</span>
        <span class="c1"># 直接调用 _handle_dns_resolved</span>
        <span class="c1"># DNS 解析 self._chosen_server</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">_dns_resolver</span><span class="o">.</span><span class="n">resolve</span><span class="p">(</span><span class="nb">self</span><span class="o">.</span><span class="n">_chosen_server</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span><span class="p">,</span>
                                   <span class="nb">self</span><span class="o">.</span><span class="n">_handle_dns_resolved</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="c1"># 去除 header 在保存到 Buffer 中</span>
        <span class="k">if</span> <span class="n">len</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="o">&gt;</span> <span class="ss">header_length</span><span class="p">:</span>
            <span class="c1"># 这里来解析 remote，即 DST.ADDR</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">_data_to_write_to_remote</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">data</span><span class="o">[</span><span class="ss">header_length</span><span class="p">:</span><span class="o">]</span><span class="p">)</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">_dns_resolver</span><span class="o">.</span><span class="n">resolve</span><span class="p">(</span><span class="n">remote_addr</span><span class="p">,</span>
                                   <span class="nb">self</span><span class="o">.</span><span class="n">_handle_dns_resolved</span><span class="p">)</span></code></pre></div>
<p>上述代码中解释几个地方：</p>
<ol>
  <li>在组成 <code>header_result</code> 中，<code>ATYP</code>、<code>DST.ADDR</code> 和 <code>DST.PORT</code> 是我们在上一篇文中提到的 SOCKS5 的请求格式中的数据划分（下表附出）。在 Shadowsocks 中预定 <code>ATYP</code>、<code>DST.ADDR</code> 和 <code>DST.PORT</code> 为 <code>header</code>，其长度 <code>header_length</code> 为 <code>1 + n + 2</code>。</li>
  <li>在取出 <code>header</code> 之后，需要对 <code>sslocal</code> 和 <code>ssserver</code> 做出划分。如果是 <code>sslocal</code>，需要先发送 SOCKS5 相应，完成握手全部过程。然后数据保存在 Buffer 之前需要对数据加密，而数据组装完成是直接发送的，不再进行加密操作。数据发送给 <code>ssserver</code>，DNS 需要解析 <code>self._chosen_server</code>。</li>
  <li>如果是 <code>ssserver</code>，首先要取出 <code>header</code> 部分，然后保存到 Buffer。因为对于你要访问的目标服来说，有无这个头是无所谓的。之后 DNS 需要解析 <code>header</code> 中拿到的目标地址。</li>
</ol>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">+----+-----+-------+------+----------+----------+</span>
<span class="o">|</span><span class="no">VER</span> <span class="o">|</span> <span class="no">CMD</span> <span class="o">|</span>  <span class="no">RSV</span>  <span class="o">|</span> <span class="no">ATYP</span> <span class="o">|</span> <span class="no">DST</span><span class="o">.</span><span class="n">ADDR</span> <span class="o">|</span> <span class="no">DST</span><span class="o">.</span><span class="n">PORT</span> <span class="o">|</span>
<span class="o">+----+-----+-------+------+----------+----------+</span>
<span class="o">|</span> <span class="mi">1</span>  <span class="o">|</span>  <span class="mi">1</span>  <span class="o">|</span>   <span class="mi">1</span>   <span class="o">|</span>  <span class="mi">1</span>   <span class="o">|</span> <span class="no">Variable</span> <span class="o">|</span>    <span class="mi">2</span>     <span class="o">|</span>
<span class="o">+----+-----+-------+------+----------+----------+</span></code></pre></div>
<p>用一张图来清晰的描述 <code>_handler_stage_addr</code> 函数的整个流程：</p>
<p><img src="../assets/images/blog/15027549268791/handle_addr_new.png" alt="handle_addr_ne" /></p>
<p>其中绿色椭圆代表<strong>加密过程</strong>，蓝色箭头代表在数据传递过程中会使用 Buffer 做优化。在执行完 <code>_handle_stage_addr</code> 之后，状态转移到 <code>STAGE_DNS</code>，同时调用了 <code>_update_stream</code> ，使得当前的 <code>TCPRelayHandler</code> 停止监听任何事件。也就是说，<code>TCPRelayHandler</code> 直到 DNS 解析完成之前，<strong>处于被挂起状态</strong>。当调用 <code>self._handle_dns_resolved</code> 才重新被唤醒。</p>
<h2 id="handlednsresolved"><code>_handle_dns_resolved</code></h2>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">_handle_dns_resolved</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="n">result</span><span class="p">,</span> <span class="n">error</span><span class="p">):</span>
    <span class="k">if</span> <span class="ss">error</span><span class="p">:</span>
        <span class="c1"># 解析错误，先取出监听的 ip 和 端口，并写入log</span>
        <span class="c1"># 自身销毁</span>
        <span class="n">addr</span><span class="p">,</span> <span class="n">port</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">_client_address</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span><span class="p">,</span> <span class="nb">self</span><span class="o">.</span><span class="n">_client_address</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span>
        <span class="n">logging</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s1">&#39;%s when handling connection from %s:%d&#39;</span> <span class="o">%</span>
                      <span class="p">(</span><span class="n">error</span><span class="p">,</span> <span class="n">addr</span><span class="p">,</span> <span class="n">port</span><span class="p">))</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">destroy</span><span class="p">()</span>
        <span class="k">return</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="p">(</span><span class="n">result</span> <span class="ow">and</span> <span class="n">result</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span><span class="p">):</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">destroy</span><span class="p">()</span>
        <span class="k">return</span>
    <span class="n">ip</span> <span class="o">=</span> <span class="n">result</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span>
    <span class="c1"># 状态改变</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">_stage</span> <span class="o">=</span> <span class="no">STAGE_CONNECTING</span>
    <span class="c1"># 获取 address</span>
    <span class="n">remote_addr</span> <span class="o">=</span> <span class="n">ip</span>
    <span class="c1"># 根据获取端口号</span>
    <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">_is_local</span><span class="p">:</span>
        <span class="n">remote_port</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">_chosen_server</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="n">remote_port</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">_remote_address</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span>
    <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">_is_local</span> <span class="ow">and</span> <span class="nb">self</span><span class="o">.</span><span class="n">_config</span><span class="o">[</span><span class="s1">&#39;fast_open&#39;</span><span class="o">]</span><span class="p">:</span>
        <span class="c1"># 这里是 TFO </span>
        <span class="c1"># 如果发现有 TFO 相关配置，则简化握手</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">_stage</span> <span class="o">=</span> <span class="no">STAGE_CONNECTING</span>
        <span class="c1"># 无需等待创建</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">_update_stream</span><span class="p">(</span><span class="no">STREAM_UP</span><span class="p">,</span> <span class="no">WAIT_STATUS_READING</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="c1"># 正常握手连接</span>
        <span class="n">remote_sock</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">_create_remote_socket</span><span class="p">(</span><span class="n">remote_addr</span><span class="p">,</span>
                                                 <span class="n">remote_port</span><span class="p">)</span>
        <span class="ss">try</span><span class="p">:</span>
            <span class="c1"># 开始连接</span>
            <span class="n">remote_sock</span><span class="o">.</span><span class="n">connect</span><span class="p">((</span><span class="n">remote_addr</span><span class="p">,</span> <span class="n">remote_port</span><span class="p">))</span>
        <span class="n">except</span> <span class="p">(</span><span class="no">OSError</span><span class="p">,</span> <span class="no">IOError</span><span class="p">)</span> <span class="n">as</span> <span class="ss">e</span><span class="p">:</span>
            <span class="k">if</span> <span class="n">eventloop</span><span class="o">.</span><span class="n">errno_from_exception</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="o">==</span> <span class="p">\</span>
                    <span class="n">errno</span><span class="o">.</span><span class="n">EINPROGRESS</span><span class="p">:</span>
                <span class="n">pass</span>
        <span class="c1"># 增加 ERR 和 OUT 事件的监听</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">_loop</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">remote_sock</span><span class="p">,</span>
                       <span class="n">eventloop</span><span class="o">.</span><span class="n">POLL_ERR</span> <span class="o">|</span> <span class="n">eventloop</span><span class="o">.</span><span class="n">POLL_OUT</span><span class="p">,</span>
                       <span class="nb">self</span><span class="o">.</span><span class="n">_server</span><span class="p">)</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">_stage</span> <span class="o">=</span> <span class="no">STAGE_CONNECTING</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">_update_stream</span><span class="p">(</span><span class="no">STREAM_UP</span><span class="p">,</span> <span class="no">WAIT_STATUS_READWRITING</span><span class="p">)</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">_update_stream</span><span class="p">(</span><span class="no">STREAM_DOWN</span><span class="p">,</span> <span class="no">WAIT_STATUS_READING</span><span class="p">)</span></code></pre></div>
<p>上述代码中的过程是检测 DNS 解析结果，如果出现错误则直接销毁；否贼会传建一个与远端通信的 Socket ，连接到 <code>remote_addr</code> 的 <code>remote_port</code> z合格端口上。然后在实践循环中监听 <code>POLL_OUT</code> 可写事件的监听，并将状态机状态切换到 <code>STAGE_CONNECTING</code>。</p>
<h3 id="handlestageconnecting"><code>_handle_stage_connecting</code></h3>
<p>在 <code>_handle_stage_connecting</code> 过程中，作用是将传递过来的当前 <code>data</code> 添加到 Buffer 中。当起始地点为 <code>sslocal</code> 则需要进行加密再添加。这之中的代码较长，但是其目的是为了处理 <em>TCP Fast Open (TFO)</em> 和 <em>One Time Auth</em> 配置的相关逻辑，其核心的步骤仅仅是如下代码：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">_is_local</span><span class="p">:</span>
    <span class="n">data</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">_encryptor</span><span class="o">.</span><span class="n">encrypt</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="nb">self</span><span class="o">.</span><span class="n">_data_to_write_to_remote</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">data</span><span class="p">)</span></code></pre></div>
<h3 id="handlestagestream"><code>_handle_stage_stream</code></h3>
<p>在 <code>_handle_stage_stream</code> 过程中，是对传递来的 <code>data</code> 进行发送的过程。同样的在 <code>sslocal</code> 为起点时，严先进行加密。</p>
<h2 id="stream">数据流动（Stream）</h2>
<p>在 Handler 的代码中，我们会发现略过了很多关于 Stream 的分析。例如 <code>_update_stream</code> 具体发生了什么等等。为了彻底搞明白，再引入一个 <em>Stream</em> 的概念。</p>
<p>对于每一个 Handler，数据都会有两个方向：</p>
<ul>
  <li>upstream（上游）：从客户端到服务器，用于本地数据读取，远程端写入；</li>
  <li>downstream（下游）：从服务器到客户端，用于本地数据写入，远程端读取。</li>
</ul>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">STREAM_UP</span> <span class="o">=</span> <span class="mi">0</span>
<span class="no">STREAM_DOWN</span> <span class="o">=</span> <span class="mi">1</span></code></pre></div>
<p>并且对于每个 Stream，都有可能处于以下状态：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">WAIT_STATUS_INIT</span> <span class="o">=</span> <span class="mi">0</span>            <span class="c1"># 初始状态</span>
<span class="no">WAIT_STATUS_READING</span> <span class="o">=</span> <span class="mi">1</span>         <span class="c1"># 写状态</span>
<span class="no">WAIT_STATUS_WRITING</span> <span class="o">=</span> <span class="mi">2</span>         <span class="c1"># 读状态</span>
<span class="no">WAIT_STATUS_READWRITING</span> <span class="o">=</span> <span class="no">WAIT_STATUS_READING</span> <span class="o">|</span> <span class="no">WAIT_STATUS_WRITING</span> <span class="c1"># 读写状态</span></code></pre></div>
<p>如果想控制 Stream 的状态并使其反映到其他代码位置，做出正确的判断，在整个的事件循环中，也就自然地要维护一个 Stream 的状态机了。<strong><code>_update_stream</code> 会在 <code>_downstream_status</code> 和 <code>_upstream_status</code> 发生变化的时候，更新 Socket 监听的事件。而具体的更新操作全部交给了上面讲述的 Handler 中来维护</strong>。简化一下 Handler 的实现逻辑，单看 Stream 的状态更新：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># 这段代码的注释来自：https://loggerhead.me/posts/shadowsocks-yuan-ma-fen-xi-tcp-dai-li.html</span>
<span class="c1"># 其描述的是 events 状态二元组 (X, Y) 表示</span>
<span class="c1"># X：_local_sock 监听的事件</span>
<span class="c1"># Y：_remote_sock 监听的事件</span>
<span class="k">def</span> <span class="nf">_handle_stage_addr</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
    <span class="c1"># ( , IO)</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">_update_stream</span><span class="p">(</span><span class="no">STREAM_UP</span><span class="p">,</span> <span class="no">WAIT_STATUS_WRITING</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_handle_dns_resolved</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="n">result</span><span class="p">,</span> <span class="n">error</span><span class="p">):</span>
    <span class="c1"># (I, IO)</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">_update_stream</span><span class="p">(</span><span class="no">STREAM_UP</span><span class="p">,</span> <span class="no">WAIT_STATUS_READWRITING</span><span class="p">)</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">_update_stream</span><span class="p">(</span><span class="no">STREAM_DOWN</span><span class="p">,</span> <span class="no">WAIT_STATUS_READING</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_on_local_write</span><span class="p">(</span><span class="nb">self</span><span class="p">):</span>
    <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">_data_to_write_to_local</span><span class="p">:</span>
        <span class="o">.</span><span class="n">.</span><span class="o">.</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="c1"># self._remote_sock: POLL_IN</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">_update_stream</span><span class="p">(</span><span class="no">STREAM_DOWN</span><span class="p">,</span> <span class="no">WAIT_STATUS_READING</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_on_remote_write</span><span class="p">(</span><span class="nb">self</span><span class="p">):</span>
    <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">_data_to_write_to_remote</span><span class="p">:</span>
        <span class="o">.</span><span class="n">.</span><span class="o">.</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="c1"># self._local_sock: POLL_IN</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">_update_stream</span><span class="p">(</span><span class="no">STREAM_UP</span><span class="p">,</span> <span class="no">WAIT_STATUS_READING</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_write_to_sock</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">sock</span><span class="p">):</span>
    <span class="k">if</span> <span class="ss">uncomplete</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">sock</span> <span class="o">==</span> <span class="nb">self</span><span class="o">.</span><span class="n">_local_sock</span><span class="p">:</span>
            <span class="c1"># self._local_sock: POLL_OUT</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">_update_stream</span><span class="p">(</span><span class="no">STREAM_DOWN</span><span class="p">,</span> <span class="no">WAIT_STATUS_WRITING</span><span class="p">)</span>
        <span class="n">elif</span> <span class="n">sock</span> <span class="o">==</span> <span class="nb">self</span><span class="o">.</span><span class="n">_remote_sock</span><span class="p">:</span>
            <span class="c1"># self._remote_sock: POLL_OUT</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">_update_stream</span><span class="p">(</span><span class="no">STREAM_UP</span><span class="p">,</span> <span class="no">WAIT_STATUS_WRITING</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">sock</span> <span class="o">==</span> <span class="nb">self</span><span class="o">.</span><span class="n">_local_sock</span><span class="p">:</span>
            <span class="c1"># self._remote_sock: POLL_IN</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">_update_stream</span><span class="p">(</span><span class="no">STREAM_DOWN</span><span class="p">,</span> <span class="no">WAIT_STATUS_READING</span><span class="p">)</span>
        <span class="n">elif</span> <span class="n">sock</span> <span class="o">==</span> <span class="nb">self</span><span class="o">.</span><span class="n">_remote_sock</span><span class="p">:</span>
            <span class="c1"># self._local_sock: POLL_IN</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">_update_stream</span><span class="p">(</span><span class="no">STREAM_UP</span><span class="p">,</span> <span class="no">WAIT_STATUS_READING</span><span class="p">)</span></code></pre></div>
<p>想对着 Events 描述的二元组 <code>(self._local_sock, self._remote_sock)</code> 监听事件，其 Stream 也发生了状态的转移，我们使用 <code>(self._downstream_status, self._upstream_status)</code> 来描述这个二元组来描述 Stream。</p>
<table>
  <thead>
    <tr>
      <th>Stream</th>
      <th>Event</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>(R, R)</code></td>
      <td><code>(I, I)</code></td>
    </tr>
    <tr>
      <td><code>(R, W)</code></td>
      <td><code>(NA, IO)</code></td>
    </tr>
    <tr>
      <td><code>(R, RW)</code></td>
      <td><code>(I, IO)</code></td>
    </tr>
    <tr>
      <td><code>(W, R)</code></td>
      <td><code>(IO, NA)</code></td>
    </tr>
  </tbody>
</table>
<blockquote>
  <p>在 Event 出现空缺 <code>NA</code> 的情况时，只监听 <code>POLL_ERR</code> 事件。</p>
</blockquote>
<p>加入了 Stream 的概念，我们来重新关注一下之前的 Handler 的 <code>_handle_stage_addr</code> 和 <code>_handle_dns_resolved</code> 这两个过程：</p>
<ul>
  <li><code>_handle_stage_addr</code>：因为此时 <code>_remote_sock</code> 为 <code>None</code> （因为 <code>_remote_sock</code> 拿到 address 和 port，而 <code>_local_sock</code> 已经完成连接需要的全部过程），此时只有 <code>_local_sock</code> 监听 <code>POLL_ERR</code> 事件。且这个阶段会使用 <code>dns_resolver</code> 方法解析远端的 IP 地址，并在回调中创建一个 <code>_remote_sock</code> 连接到远端。当然，DNS 如果解析失败，再有数据也是无意义的。</li>
  <li><code>_handle_dns_resolved</code>：此时已经得到了远端的 IP 地址（从 SOCK5 请求的 header 中获取），并尝试与远端的地址建立 TCP 连接。<code>remote_sock.connect</code> 这个方法是非阻塞（<code>remote_sock.setblocking(False)</code> 为开关函数）的，这样无法得知什么时候连接成功，所以在 <code>_remote_sock</code> 上注册 <code>POLL_OUT</code> 事件，当事件循环通知 <code>_remote_sock</code> 可写了，说明 connect 成功。这时候将 <code>_local_sock</code> 注册为 <code>POLL_IN</code>，将数据加入 Buffer 执行 <code>_handle_stage_connecting</code> ，从而大大优化了因为连接不定时性造成的数据传输延迟。</li>
</ul>
<p><img src="../assets/images/blog/15027549268791/handle_event.png" alt="handle_event" /></p>
<p>而对于 <code>_on_local_write</code> / <code>_on_remote_write</code> 和 <code>_write_sock</code> 方法，在 <a href="https://loggerhead.me/posts/shadowsocks-yuan-ma-fen-xi-tcp-dai-li.html#fn:non-blocking socket">loggerhead 大神的博客</a> 中也描述的十分清楚，这里我直接引用来：</p>
<ul>
  <li><code>_on_local_write</code> / <code>_on_remote_write</code>：这两个函数是十分相似的。它们会将缓冲区的数据发送出去，如果缓冲区空了，则将另一个套接字注册为可读，因为只有它可读才可能重新塞入数据到缓冲区。有一点要注意，<code>_on_remote_write</code> 被调用说明 <code>self._remote_sock</code> 成功建立了连接，此时它会将 <code>self._stage </code>设置为 <code>STAGE_STREAM</code>；</li>
  <li><code>_write_sock</code>：负责将数据通过给定套接字发送出去，并改变对应的监听事件。如果全部发送成功，此时的动作和 <code>_on_XXX_write</code> 一样，将另一个套接字注册为可读；如果没有全部发送成功，则将剩余的数据添加到缓冲区，并将套接字注册为可写。</li>
</ul>
<h2 id="section-1">概括</h2>
<p><img src="http://www.desgard.com/iOS-Source-Probe/Python/Shadowsocks/media/15027549268791/ss-event-stage-relationship.svg" alt="ss-event-stage-relationship" /></p>
<p>（该图摘自 <a href="https://loggerhead.me/posts/shadowsocks-yuan-ma-fen-xi-tcp-dai-li.html">Shadowsocks 源码分析——TCP 代理</a> 但是感觉有一些问题）</p>
<p>在 Shadowsocks 中最关键的无疑就是 <code>TCPRelayHandler</code>。事件循环驱动着整个工程的运行，而 <code>TCPRelayHandler</code> 保证每个事件的正常处理。这一部分内容在本篇文章中只能介绍最主要的部分，即数据、事件、状态的传递与转换。而涉及到的其他处理，例如 <strong>Time out</strong>、<strong>TFO</strong>、<strong>One Time Auth</strong> 等在之后的文章中会陆续加上这些。</p>
<h2 id="section-2">引文</h2>
<ul>
  <li><a href="https://loggerhead.me/posts/shadowsocks-yuan-ma-fen-xi-tcp-dai-li.html">Shadowsocks 源码分析——TCP 代理</a></li>
</ul>
  <footer class="post-footer">
    <section class="author">
      <h4>Desgard_Duan</h4>
      <p>I write many code to write less code.💻</p>
    </section>
<aside class="share">
  <h4>Share this.</h4>
  <a href="http://twitter.com/share?text=《Shadowsocks Probe II - TCP 代理过程》 -- Guardia · 瓜地&amp;url=https://www.desgard.com/Shadowsocks-2/"
  onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-twitter-square"></i>
  </a>
  <a href="http://v.t.sina.com.cn/share/share.php?url=https://www.desgard.com/Shadowsocks-2/&amp;title=《Shadowsocks Probe II - TCP 代理过程》 —— Guardia · 瓜地" onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-weibo"></i>
  </a>
</aside>
      <hr>
<aside id="comments" class="disqus">
  <div id="container"></div>
  <!-- <link rel="stylesheet" href="/assets/css/gitment.css">
  <script src="https://imsun.github.io/gitment/dist/gitment.browser.js"></script>
  <script>
  var gitment = new Gitment({
    id: "https://www.desgard.com/Shadowsocks-2/", 
    owner: 'Desgard',
    repo: 'desgard.github.com',
    oauth: {
      client_id: 'e2612df42f3f2a83e71c',
      client_secret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
    },
  })
  gitment.render('container')
  </script> -->
<link rel="stylesheet" href="/assets/css/gitalk.css">
<script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
<div id='gitalk-container'></div>
<script>
    const gitalk = new Gitalk({
        id: "https://www.desgard.com/Shadowsocks-2/", 
        clientID: 'e2612df42f3f2a83e71c',
        clientSecret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
        repo: 'desgard.github.com',
        owner: 'Desgard',
        admin: ['Desgard'],
        // facebook-like distraction free mode
        distractionFreeMode: false
    })
    gitalk.render('gitalk-container')
</script>
</aside>
  </footer>
</article>
    </main>
<footer class="footer">
  <div class="container">
    <ul class="icons">
      <li>
        <a href="https://github.com/desgard" class="icon-github" target="_blank">
          <i class="fa fa-github"></i>
        </a>
      </li>
      <li>
        <a href="https://www.facebook.com/desgard.duan" class="icon-facebook" target="_blank">
          <i class="fa fa-facebook"></i>
        </a>
      </li>
      <li>
        <a href="https://twitter.com/Desgard_Duan" class="icon-twitter" target="_blank">
          <i class="fa fa-twitter"></i>
        </a>
      </li>
      <li>
        <a href="https://stackoverflow.com/users/6119149/desgard-duan" class="icon-github" target="_blank">
          <i class="fa fa-stack-overflow"></i>
        </a>
      </li>
      <li>
        <a href="https://weibo.com/desgard" class="icon-instagram" target="_blank">
          <i class="fa fa-weibo"></i>
        </a>
      </li>
    </ul>
    <p>
      <q>I write many code to write less code.💻</q>
      <small>– Gua</small>
    </p>
    <small class="clearfix">
      Powered by <a href="http://jekyllrb.com" target="_blank">Jekyll</a> • <a href="https://github.com/desgard" target="_blank">Open source <i class="fa fa-heart"></i></a>
    </small>
  </div>
</footer>
<a class="scroll-up fa fa-chevron-up bounce" href="#" data-position="0"></a>
<div id="modalSearch" class="modal">
  <div class="modal__overlay"></div>
  <div class="modal__content">
    <a href="#!" class="modal-close" data-modal-close>&times;</a>
    <div class="search-container">
      <input type="text" id="search-input" placeholder="Search articles">
      <ul id="results-container"></ul>
    </div>
  </div>
</div>
    <script src="/assets/main-52d417e8a6ff9f5b168386d37c96338a.js"></script>
  </body>
  <script>
    var link = "" ;
    var os = function() {  
      var ua = navigator.userAgent,  
      isWindowsPhone = /(?:Windows Phone)/.test(ua),  
      isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,   
      isAndroid = /(?:Android)/.test(ua),   
      isFireFox = /(?:Firefox)/.test(ua),   
      isChrome = /(?:Chrome|CriOS)/.test(ua),  
      isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),  
      isPhone = /(?:iPhone)/.test(ua) && !isTablet,  
      isPc = !isPhone && !isAndroid && !isSymbian;  
      return {  
        isTablet: isTablet,  
        isPhone: isPhone,  
        isAndroid : isAndroid,  
        isPc : isPc  
      };  
    }();  
    if (link.length > 0) {
      if (os.isAndroid || os.isPhone) {
        location.replace(link);
      }
    }
  </script>
</html>